﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
using DirectShowLib;
using System.IO;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using D3D = Microsoft.DirectX.Direct3D;
using System.Reflection;
using Tsanie.DmPoster.Utils;
using System.Diagnostics;
using Tsanie.DmPoster.Models;

namespace Tsanie.DmPoster {
    public partial class PlayerForm : Form {

        #region - 静态字段 -
        // 窗口名常量
        private const string c_PlayerText = " - 迷你播放器";
        // 静态全局引用
        internal static PlayerForm _player = null;
        // 记录打开文件时DirectShow函数初始化是否发生错误, 错误就直接关闭
        internal static bool _error = false;
        #endregion

        #region - 类字段 -
        /* 记录fps用 */
        private int frames = 0;
        private int fps = 0;
        // 打开的视频文件路径
        private string file;
        // 视频总长度
        private double duration;
        // 当前视频播放进度
        private double current;
        // 高精度计时器, 用来控制刷新率用
        private Stopwatch sw = null;
        // 因为这里计算fps是0.5秒一次, 所以再用了一个当前视频播放位置的临时变量
        private double nowPosition = 0;
        // 随机数工具
        private Random random = new Random();

        // 弹幕
        private CommentManager<BiliComment> _comments = null;
        /* 自定义的排序链表, 存储当前显示的各条弹幕的高度, 共4种模式(置顶,底端,滚动,逆向滚动) */
        private SortedLinkedList _listT = null;
        private SortedLinkedList _listB = null;
        private SortedLinkedList _listS = null;
        private SortedLinkedList _listNS = null;
        // 弹幕这里是绘制成Texture来显示的, 这个是弹幕的缓存字典
        private Dictionary<BiliComment, Texture> _cmtTextures = null;
        // 字号对应字体的缓存字典
        private Dictionary<int, System.Drawing.Font> _gdiFonts = null;

        // 缓存加载线程(Texture)
        private Thread _threadLoader = null;
        // 缓存释放线程(Texture)
        private Thread _threadDisposer = null;
        // 加载和释放线程循环的控制变量, 值为false时退出循环, 结束线程, 算是软控制
        private bool _threadLoaing = false;

        // 播放状态
        private PlayState playState = PlayState.Init;
        /* 进度条拖拽的临时变量 */
        private Point ptCursor = Point.Empty;
        private bool isDragging = false;
        #endregion

        #region - DirectX object -
        // D3D 物件
        private D3D.Font d3dFont = null;    // 播放器绘制字体
        private Sprite sprite = null;       // DX精灵对象(绘制各种元件,比如进度条,提示文字等)
        private Line ln = null;             // 这东西应该是绘制弹幕周围的框框(选中一条弹幕时会在那个弹幕周围画个框框)
        private Point ptTime = new Point(10, 400);  // 播放器提示文字的显示位置(<空格> 播放/暂停，<A> 插入弹幕，<F> 单帧播放)
        private Texture texTrackbar = null;         // 进度条材质
        private Vector3 posTrackbar = new Vector3(0, 386, 0);   // 进度条显示位置
        private Texture texTime = null;             // 进度条上的当前位置那个点的材质
        private Texture texTimePress = null;        // 按住鼠标拖放时的那个点的材质
        private Vector3 posTime = new Vector3(5, 388, 0);   // 播放时间显示位置

        // 托管 D3D 设备
        private Device device = null;   // 虚拟设备
        private PresentParameters presentParams = null; // 初始化参数
        // 托管 D3D 资源
        private Surface renderTarget;   // 渲染面
        private VertexBuffer vertextBuffer = null;  // 向量缓存
        private CustomVertex.TransformedTextured[] vertices = new CustomVertex.TransformedTextured[4];  // 神马自定义变换向量, 我也没理解, 是DirectShow的例子给出的.
        // 后台缓存固定大小
        private Size displaySize = new Size(542, 386);
        // DirectShow 对象
        private IFilterGraph2 graphBuilder;
        private IMediaControl mediaControl;
        private IMediaPosition mediaPosition;
        private IMediaEventEx mediaEvent;
        private IBaseFilter vmr9;
        private IVideoFrameStep videoStep;
        private Allocator allocator;
        #endregion

        private const int WM_PAINT = 0x000F;
        private const int WM_GRAPHNOTIFY = 0x4000 + 123;

        private PlayerForm(string file) {
            InitializeComponent();
            try {
                this.SetStyle(ControlStyles.AllPaintingInWmPaint
                    | ControlStyles.UserPaint
                    | ControlStyles.Opaque, true);
                this.file = file;
                this._comments = Program.m_MainForm._comments;
                this._listT = new SortedLinkedList();
                this._listB = new SortedLinkedList();
                this._listS = new SortedLinkedList();
                this._listNS = new SortedLinkedList();
                this._cmtTextures = new Dictionary<BiliComment, Texture>();
                this._gdiFonts = new Dictionary<int, System.Drawing.Font>();
            } catch (Exception e) {
                MessageBox.Show(this, "初始化播放器时发生异常！" + e.Message, "异常",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                PlayerForm._error = true;
                return;
            }
            sw = new Stopwatch();
            InitializeThreads();
        }
        protected override void Dispose(bool disposing) {
            if (disposing) {
                if (components != null)
                    components.Dispose();
                _player = null;
                _threadLoaing = false;
                if (_threadLoader.ThreadState == System.Threading.ThreadState.Running) {
                    _threadLoader.Abort();
                }
                if (_threadDisposer.ThreadState == System.Threading.ThreadState.Running) {
                    _threadDisposer.Abort();
                }
                Shutdown();
            }
            base.Dispose(disposing);
        }
        private void InitializeThreads() {
            // 预读弹幕材质
            _threadLoaing = true;
            float seconds = Config.m_CacheSeconds; // 缓存的未来秒数
            this._threadLoader = new Thread(() => {
                while (_threadLoaing) {
                    _comments.ForEach((comm) => {
                        if ((current <= comm.PlayTime)
                            && (current + seconds > comm.PlayTime)) {
                            // 未来seconds秒内的弹幕预先载入
                            if (!_cmtTextures.ContainsKey(comm)) {
                                CreateCommentTexture(comm);
                            }
                        }
                        if (!_threadLoaing)
                            return false;
                        return true;
                    });
                    Thread.Sleep(10);
                }
            });
            // 释放线程
            this._threadDisposer = new Thread(() => {
                while (_threadLoaing) {
                    _comments.ForEach((comm) => {
                        if (current > comm.PlayTime + comm.Life + 0.1
                            //|| current + seconds < comm.PlayTime
                            ) {
                            // 已播放的弹幕清除缓存
                            Texture tex;
                            if (_cmtTextures.TryGetValue(comm, out tex)) {
                                _cmtTextures.Remove(comm);
                                tex.Dispose();
                            }
                            tex = null;
                        }
                        if (!_threadLoaing)
                            return false;
                        return true;
                    });
                    Thread.Sleep(50);
                }
            });
        }

        #region - 窗体事件 -
        private void PlayerForm_Load(object sender, EventArgs e) {
            this.Location = new Point(Program.m_MainForm.Left + 300,
                Program.m_MainForm.Top - 30);
        }
        private void PlayerForm_Shown(object sender, EventArgs e) {
            OpenFile(file);
        }
        private void PlayerForm_FormClosing(object sender, FormClosingEventArgs e) {
            _threadLoaing = false;
        }
        private void PlayerForm_MouseMove(object sender, MouseEventArgs e) {
            if (CanDrag(e.Location)) {
                this.Cursor = Cursors.Hand;
            } else if (CanAdd(e.Location)) {
                this.Cursor = Cursors.Cross;
            } else {
                this.Cursor = Cursors.Default;
            }
            if (isDragging) {
                ptCursor = e.Location;
            }
        }
        private void PlayerForm_MouseDown(object sender, MouseEventArgs e) {
            if (CanDrag(e.Location)) {
                ptCursor = e.Location;
                isDragging = true;
            }
        }
        private void PlayerForm_MouseUp(object sender, MouseEventArgs e) {
            if (isDragging) {
                isDragging = false;
                if (OutForm(e.Location))
                    return;
                int x = ptCursor.X;
                if (x < 10)
                    x = 10;
                else if (x > 532)
                    x = 532;
                double current = ((float)(x - 10) / 522f) * duration;
                SetPosition(current);
            } else if (e.Button == System.Windows.Forms.MouseButtons.Right
                && CanAdd(e.Location)) {
                BiliPosComment comm;
                if (mediaPosition != null) {
                    if (playState == PlayState.Running)
                        PauseGraph();
                    current = GetCurrent();
                }
                if (CommentForm._commentForm != null) {
                    // 当前有弹幕编辑窗体
                    if ((CommentForm._commentForm._comment is BiliPosComment)
                        && (CommentForm._commentForm._comment.Mode == 7)) {
                        comm = CommentForm._commentForm._comment as BiliPosComment;
                        comm.X = e.X;
                        comm.Y = e.Y;
                        comm.PlayTime = (float)(Math.Floor(current * 100)) / 100f;
                        CommentForm._commentForm.Invoke(new MethodInvoker(() => {
                            CommentForm._commentForm.ReadComment(comm);
                            CommentForm._commentForm.Save();
                            NativeMethods.SetForegroundWindow(CommentForm._commentForm.Handle);
                        }));
                        return;
                    }
                }
                comm = new BiliPosComment();
                comm.PlayTime = (float)(Math.Floor(current * 100)) / 100f;
                comm.Mode = 7;
                comm.Color = Config.m_color;
                comm.Fontsize = Config.m_fontsize;
                comm.Message = "[\"" + e.X + "\",\"" + e.Y
                    + "\",\"1-1\",\"4.5\",\"text\",\"0\",\"0\"]";
                Program.m_MainForm.Invoke(new MethodInvoker(() => {
                    CommentForm.ShowCommentForm(comm, m => {
                        Program.m_MainForm.Edited(Program.m_MainForm.CurrentRowIndex(), m);
                    }, null);
                    NativeMethods.SetForegroundWindow(CommentForm._commentForm.Handle);
                }));
            }
        }
        private void PlayerForm_KeyUp(object sender, KeyEventArgs e) {
            if (mediaControl != null) {
                if (e.KeyCode == Keys.Space) {
                    if (playState == PlayState.Running) {
                        PauseGraph();
                    } else {
                        RunGraph();
                    }
                } else if (e.KeyCode == Keys.Left) {
                    SetPosition(GetCurrent() - 0.5);
                } else if (e.KeyCode == Keys.Right) {
                    SetPosition(GetCurrent() + 0.5);
                } else if (e.KeyCode == Keys.PageUp) {
                    SetPosition(GetCurrent() - 5);
                } else if (e.KeyCode == Keys.PageDown) {
                    SetPosition(GetCurrent() + 5);
                } else if (e.KeyCode == Keys.A) {
                    AddSubtitle();
                }
            }
        }
        #endregion

        public static void ShowPlayer(string file) {
            if (_player == null) {
                Thread initThread = new Thread(new ThreadStart(() => {
                    _player = new PlayerForm(file);
                    if (PlayerForm._error)
                        return;
                    _player.Show();
                    _player.Focus();
                    Application.Run(_player);
                }));
                initThread.SetApartmentState(ApartmentState.MTA);
                initThread.Start();
            }
        }
        private double GetCurrent() {
            double curr = -1;
            if (mediaPosition != null) {
                mediaPosition.get_CurrentPosition(out curr);
            }
            return curr;
        }
        private void AddSubtitle() {
            if (mediaPosition != null) {
                if (playState == PlayState.Running)
                    PauseGraph();
            }
            BiliComment comm = new BiliComment();
            comm.Mode = Config.m_mode;
            comm.Color = Config.m_color;
            comm.Fontsize = Config.m_fontsize;
            comm.PlayTime = (float)(Math.Floor(current * 100)) / 100f;
            Program.m_MainForm.Invoke(new MethodInvoker(() => {
                CommentForm.ShowCommentForm(comm, m => {
                    Program.m_MainForm.Edited(Program.m_MainForm.CurrentRowIndex(), m);
                }, null);
                NativeMethods.SetForegroundWindow(CommentForm._commentForm.Handle);
            }));
        }

        /// <summary>初始化托管 D3D 设备</summary>
        private void InitializeGraphics() {
            presentParams = new PresentParameters();
            presentParams.Windowed = true;
            // 启用 Z-Buffer
            presentParams.EnableAutoDepthStencil = true;
            presentParams.AutoDepthStencilFormat = DepthFormat.D16;
            presentParams.PresentFlag = PresentFlag.Video;
            // 缓存交换
            presentParams.SwapEffect = SwapEffect.Copy;
            presentParams.PresentationInterval = PresentInterval.Immediate;
            // 禁用托管 D3D 事件
            Device.IsUsingEventHandlers = true;
            // 创建设备
            device = new Device(
                Manager.Adapters.Default.Adapter,
                DeviceType.Hardware,
                this,
                CreateFlags.SoftwareVertexProcessing | CreateFlags.MultiThreaded,
                presentParams
                );

            FontDescription desc = new FontDescription();
            desc.FaceName = "Tahoma";
            desc.Height = 15;
            d3dFont = new D3D.Font(device, desc);
            sprite = new Sprite(device);
            ln = new Line(device);
            ln.Width = 1;
            ln.Antialias = false;
            Assembly ass = Assembly.GetExecutingAssembly();
            using (Stream stream = ass.GetManifestResourceStream("Tsanie.DmPoster.Resources.trackbar.png")) {
                texTrackbar = TextureLoader.FromStream(device, stream, -1, -1, -1, -1, 0, Format.A8R8G8B8, 0, Filter.None, Filter.None, 0);
            }
            using (Stream stream = ass.GetManifestResourceStream("Tsanie.DmPoster.Resources.pos.png")) {
                texTime = TextureLoader.FromStream(device, stream, -1, -1, -1, -1, 0, Format.A8R8G8B8, 0, Filter.None, Filter.None, 0);
            }
            using (Stream stream = ass.GetManifestResourceStream("Tsanie.DmPoster.Resources.posPress.png")) {
                texTimePress = TextureLoader.FromStream(device, stream, -1, -1, -1, -1, 0, Format.A8R8G8B8, 0, Filter.None, Filter.None, 0);
            }

            AllocateResources();
            InitializeDevice();

            // 开始缓存弹幕
            this._threadLoader.Start();
            this._threadDisposer.Start();
        }
        private void AllocateResources() {
            // 接受渲染目标（后台缓存）
            renderTarget = device.GetRenderTarget(0);
            // 分配一个顶点缓存绘制视频
            vertextBuffer = new VertexBuffer(
                typeof(CustomVertex.TransformedTextured),
                4,
                device,
                Usage.Dynamic | Usage.WriteOnly,
                CustomVertex.TransformedTextured.Format,
                Pool.Default
                );
        }
        private void InitializeDevice() {
            FilterCaps filterCaps = device.DeviceCaps.TextureFilterCaps;
            // 如果硬件支持，则应用一个 bilinear 滤镜
            if (filterCaps.SupportsMagnifyLinear && filterCaps.SupportsMinifyLinear) {
                device.SamplerState[0].MinFilter = TextureFilter.Linear;
                device.SamplerState[0].MagFilter = TextureFilter.Linear;
            } else {
                device.SamplerState[0].MinFilter = TextureFilter.Point;
                device.SamplerState[0].MagFilter = TextureFilter.Point;
            }
        }
        private void InitVMR9(string file) {
            int hr = 0;
            // 创建一个 DirectShow FilterGraph
            graphBuilder = (IFilterGraph2)new FilterGraph();
            mediaControl = (IMediaControl)graphBuilder;
            mediaPosition = (IMediaPosition)graphBuilder;
            mediaEvent = graphBuilder as IMediaEventEx;
            videoStep = graphBuilder as IVideoFrameStep;
            // 添加事件监听（WndProc）
            hr = mediaEvent.SetNotifyWindow(this.Handle, WM_GRAPHNOTIFY, IntPtr.Zero);
            DsError.ThrowExceptionForHR(hr);
            // 创建 VMR9 对象
            vmr9 = (IBaseFilter)new VideoMixingRenderer9();
            IVMRFilterConfig9 filterConfig = (IVMRFilterConfig9)vmr9;
            // 无渲染模式
            hr = filterConfig.SetRenderingMode(VMR9Mode.Renderless);
            DsError.ThrowExceptionForHR(hr);
            // 创建 Allocator/Presenter 对象
            allocator = new Allocator(device);
            IVMRSurfaceAllocatorNotify9 vmrSurfAllocNotify = (IVMRSurfaceAllocatorNotify9)vmr9;
            // 通知 VMR9 滤镜
            hr = vmrSurfAllocNotify.AdviseSurfaceAllocator(IntPtr.Zero, allocator);
            DsError.ThrowExceptionForHR(hr);
            // 通知 allocator
            hr = allocator.AdviseNotify(vmrSurfAllocNotify);
            DsError.ThrowExceptionForHR(hr);
            // 使用混合器
            hr = filterConfig.SetNumberOfStreams(1);
            DsError.ThrowExceptionForHR(hr);
            IVMRMixerControl9 mixerControl = (IVMRMixerControl9)vmr9;
            hr = mixerControl.SetMixingPrefs(VMR9MixerPrefs.RenderTargetYUV | VMR9MixerPrefs.NoDecimation | VMR9MixerPrefs.ARAdjustXorY | VMR9MixerPrefs.BiLinearFiltering);
            DsError.ThrowExceptionForHR(hr);
            // 把滤镜添加到图形中
            hr = graphBuilder.AddFilter(vmr9, "Video Mixing Renderer 9");
            DsError.ThrowExceptionForHR(hr);
            // 渲染文件
            hr = graphBuilder.RenderFile(file, null);
            DsError.ThrowExceptionForHR(hr);

            UpdateEnvironment();
        }
        private void Shutdown() {
            int hr = 0;
            if (graphBuilder != null) {
                // 停止 DirectShow 通知
                hr = mediaEvent.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
                DsError.ThrowExceptionForHR(hr);
                hr = mediaControl.StopWhenReady();
                hr = mediaControl.Stop();
                // 销毁分配器
                allocator.Dispose();
                // 释放 DirectShow 对象
                Marshal.ReleaseComObject(vmr9);
                Marshal.ReleaseComObject(mediaControl);
                Marshal.ReleaseComObject(mediaPosition);
                Marshal.ReleaseComObject(mediaEvent);
                Marshal.ReleaseComObject(videoStep);
                Marshal.ReleaseComObject(graphBuilder);
                // 材质释放
                foreach (Texture tex in _cmtTextures.Values) {
                    tex.Dispose();
                }
                graphBuilder = null;
                // 释放托管 D3D 资源
                renderTarget.Dispose();
                vertextBuffer.Dispose();
                // 释放托管 D3D 设备
                device.Dispose();
            }
        }

        /*private D3D.Font GetDXFont(int size) {
            D3D.Font font;
            if (!_dxFonts.TryGetValue(size, out font)) {
                font = CreateDXFont(size);
                _dxFonts.Add(size, font);
            }
            return font;
        }*/
        /*private D3D.Font CreateDXFont(int size) {
            FontDescription desc = new FontDescription();
            desc.CharSet = CharacterSet.GB2312;
            desc.FaceName = Options.m_Fontname;
            desc.Height = size;
            desc.Quality = FontQuality.AntiAliased;
            desc.Weight = FontWeight.Bold;
            return new D3D.Font(device, desc);
        }*/
        private System.Drawing.Font GetGDIFont(int size) {
            System.Drawing.Font font;
            if (!_gdiFonts.TryGetValue(size, out font)) {
                font = CreateGDIFont(size);
                _gdiFonts.Add(size, font);
            }
            return font;
        }
        private System.Drawing.Font CreateGDIFont(int size) {
            return new System.Drawing.Font(Config.m_Fontname, size, FontStyle.Bold, GraphicsUnit.Pixel, 134); // GB2312_CHARSET = 134
        }
        private Texture GetCommentTexture(BiliComment comm) {
            Texture texComment;
            if (!_cmtTextures.TryGetValue(comm, out texComment)) {
                texComment = CreateCommentTexture(comm);
            }
            return texComment;
        }
        private Texture CreateCommentTexture(BiliComment comm) {
            Texture texComm;
            string[] lines = null;
            try {
                if (comm.Mode == 7) {
                    BiliPosComment cmt = comm as BiliPosComment;
                    lines = cmt.RealMsg.Split(new string[] { "/n" },
                        StringSplitOptions.None);
                    if (cmt.RotateZ >= 0) {
                        // 模拟Y轴翻转
                        int tan = cmt.RotateY % 360;
                        float fx = (Math.Abs(180f - tan) - 90f) / 90f;
                        cmt.ryValue = fx;
                        cmt.rzValue = 2 * (float)Math.PI * (cmt.RotateZ / 360f);
                    }
                }
            } catch { } finally {
                if (lines == null)
                    lines = comm.Message.Split(new string[] { "/n" },
                    StringSplitOptions.None);
            }
            int height = 0;
            // 计算宽度
            int max = 0;
            foreach (string line in lines) {
                int n = StringUtil.StringWidth(line, comm.Fontsize);
                if (n > max)
                    max = n;
            }
            int width = max;
            comm.Width = width;
            // 绘制弹幕
            Bitmap bmp = new Bitmap(comm.Width + 10, lines.Length * (comm.Fontsize + 2) + 5);
            System.Drawing.Font fnt = GetGDIFont(comm.Fontsize);
            Graphics g = Graphics.FromImage(bmp);
            g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
            Brush brush = new SolidBrush(Color.FromArgb(comm.Color | -16777216));
            Brush sh = (comm.Color == 0 ? Brushes.White : Brushes.Black);
            foreach (string line in lines) {
                g.DrawString(line, fnt, sh, -1, height - 1);
                g.DrawString(line, fnt, sh, -1, height + 1);
                g.DrawString(line, fnt, sh, 1, height - 1);
                g.DrawString(line, fnt, sh, 1, height + 1);
                g.DrawString(line, fnt, brush, 0, height);
                height += comm.Fontsize + 2;
            }
            comm.Height = height;
            brush.Dispose();
            g.Dispose();
            try {
                texComm = new Texture(device, bmp, 0, Pool.Managed);
            } catch {
                texComm = new Texture(device, new Bitmap(1, 1), 0, Pool.Managed);
            }
            // 缓存
            if (_cmtTextures.ContainsKey(comm))
                _cmtTextures[comm] = texComm;
            else
                _cmtTextures.Add(comm, texComm);
            return texComm;
        }
        internal void InvaliteTexture(BiliComment comm) {
            Texture texComment;
            if (_cmtTextures.TryGetValue(comm, out texComment)) {
                _cmtTextures.Remove(comm);
                texComment.Dispose();
            }
        }

        #region - DX 绘制事件 -
        private void UpdateEnvironment() {
            Size vmrTexSize = allocator.TextureSize;
            Size vmrVidSize = allocator.VideoSize;
            Size vmrAspect = allocator.AspectRatio;
            Rectangle videoClientRect = Rectangle.Empty;
            float ar = (float)vmrAspect.Width / (float)vmrAspect.Height;
            if (ar > (512f / 384f)) {
                videoClientRect.X = 15;
                videoClientRect.Width = 512;
                videoClientRect.Height = (int)(512f / ar);
                videoClientRect.Y = (384 - videoClientRect.Height) / 2;
            } else {
                videoClientRect.Y = 1;
                videoClientRect.Width = (int)(384f * ar);
                videoClientRect.Height = 384;
                videoClientRect.X = (542 - videoClientRect.Width) / 2;
            }
            // 0 *--------* 1
            //   |╲      |
            //   |  ╲    |
            //   |    ╲  |
            //   |      ╲|
            // 3 *--------* 2
            vertices[0].X = videoClientRect.X;
            vertices[0].Y = videoClientRect.Y;
            vertices[1].X = videoClientRect.Width + videoClientRect.X;
            vertices[1].Y = videoClientRect.Y;
            vertices[2].X = videoClientRect.Width + videoClientRect.X;
            vertices[2].Y = videoClientRect.Height + videoClientRect.Y;
            vertices[3].X = videoClientRect.X;
            vertices[3].Y = videoClientRect.Height + videoClientRect.Y;

            vertices[0].Tu = 0f;
            vertices[0].Tv = 0f;
            vertices[1].Tu = (float)vmrVidSize.Width / (float)vmrTexSize.Width;
            vertices[1].Tv = 0f;
            vertices[2].Tu = (float)vmrVidSize.Width / (float)vmrTexSize.Width;
            vertices[2].Tv = (float)vmrVidSize.Height / (float)vmrTexSize.Height;
            vertices[3].Tu = 0f;
            vertices[3].Tv = (float)vmrVidSize.Height / (float)vmrTexSize.Height;
            // 填入顶点缓存
            vertextBuffer.SetData(vertices, 0, LockFlags.Discard);
        }
        private void Render() {
            sw.Reset();
            sw.Start();
            device.SetRenderTarget(0, renderTarget);
            device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1f, 0);
            device.BeginScene();
            device.SetStreamSource(0, vertextBuffer, 0);
            device.VertexFormat = CustomVertex.TransformedTextured.Format;
            device.SetTexture(0, allocator.Texture);
            // 绘制
            device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 2);
            sprite.Begin(SpriteFlags.AlphaBlend);
            // 绘制弹幕
            bool rendered = (CommentForm._commentForm == null);
            BiliComment cmt = null;
            if (!rendered) {
                cmt = CommentForm._commentForm._comment;
                rendered = (cmt.Mode != 7
                    && cmt.Mode != 1 && cmt.Mode != 6
                    && cmt.Mode != 4 && cmt.Mode != 5);
            }
            _comments.ForEach((comm) => {
                try {
                    if (comm.Mode != 7
                        && comm.Mode != 1 && comm.Mode != 6
                        && comm.Mode != 4 && comm.Mode != 5) { // 跳过弹幕
                        return true;
                    }
                    if (comm == cmt)
                        return true;
                    // 是否选中
                    bool sel = Program.m_MainForm.IsSelected(comm.Id);
                    if ((current - comm.PlayTime >= 0)
                        && (current - comm.PlayTime < comm.Life)) {
                        if (comm.Mode == 7) {
                            RenderPosComment(comm as BiliPosComment, current, device, sel);
                        } else if (comm.Mode == 5 || comm.Mode == 4) {
                            RenderFixedComment(comm, current, device, sel);
                        } else if (comm.Mode == 1 || comm.Mode == 6) {
                            RenderScrollComment(comm, current, device, sel);
                        }
                    }
                } catch { }
                return true;
            });
            if (!rendered) {
                if ((current - cmt.PlayTime >= 0)
                    && (current - cmt.PlayTime < cmt.Life)) {
                    try {
                        if (cmt.Mode == 7) {
                            RenderPosComment(cmt as BiliPosComment, current, device, true);
                        } else if (cmt.Mode == 5 || cmt.Mode == 4) {
                            RenderFixedComment(cmt, current, device, true);
                        } else if (cmt.Mode == 1 || cmt.Mode == 6) {
                            RenderScrollComment(cmt, current, device, true);
                        }
                    } catch { }
                }
            }
            // 绘制面板
            current = GetCurrent();
            if (current - nowPosition > 0.5f) {
                nowPosition = current;
                fps = frames * 2;
                frames = 0;
            }
            if (playState == PlayState.Running)
                frames++;
            sprite.Transform = Matrix.Identity;
            sprite.Draw(texTrackbar, Vector3.Empty, posTrackbar, -1);
            d3dFont.DrawText(sprite, "<空格> 播放/暂停，<A> 插入弹幕，<F> 单帧播放",
                250, ptTime.Y, Color.Gray);
            // 进度文字
            string strCurrent;
            // 522宽
            if (isDragging && !OutForm(ptCursor)) {
                int x = ptCursor.X;
                if (x < 10)
                    x = 10;
                else if (x > 532)
                    x = 532;
                posTime.X = x - 5;
                sprite.Draw(texTimePress, Vector3.Empty, posTime, -1);
                double curr = ((x - 10) / 522.0) * duration;
                strCurrent = curr.ToString("00.000")
                    + " [" + GetDurationStr(curr)
                    + "/" + GetDurationStr(duration) + "]";
            } else {
                int time = (int)(5f + 522f * (current / duration));
                posTime.X = time;
                sprite.Draw(texTime, Vector3.Empty, posTime, -1);
                strCurrent = current.ToString("00.000")
                    + " [" + GetDurationStr(current)
                    + "/" + GetDurationStr(duration) + "]";
            }
            d3dFont.DrawText(sprite, strCurrent, ptTime, Color.White);
            // 缓存进度
            //float percent = (float)_subTextures.Count / (float)_subtitles.Count;
            string text = fps.ToString() + "fps";
            //if (percent < 1) {
            int count = _cmtTextures.Count;
            string cache = "已缓存 " + count + " 条弹幕";
            if (count > 400)
                count = 400;
            count = count * 255 / 400;
            Color c = Color.FromArgb(count, 255 - count, 0);
            //}
            // 显示
            d3dFont.DrawText(sprite, text, 1, 1, Color.Black);
            d3dFont.DrawText(sprite, cache, 37, 1, Color.Black);
            d3dFont.DrawText(sprite, text, 0, 0, Color.White);
            d3dFont.DrawText(sprite, cache, 36, 0, c);
            //for (int i = 0; i < filters.Count; i++) {
            //    int height = (i + 1) * 14;
            //    d3dFont.DrawText(sprite, filters[i], 1, height + 1, Color.Black);
            //    d3dFont.DrawText(sprite, filters[i], 0, height, Color.White);
            //}

            sprite.Flush();
            sprite.End();
            device.EndScene();
            device.SetTexture(0, null);
            // 呈现
            device.Present();
            long ms = 16 - sw.ElapsedMilliseconds;
            if (ms > 0)
                Thread.Sleep((int)ms);
        }
        private void RenderPosComment(BiliPosComment comm, double current,
            Device device, bool selected) {
            // 格式不正确不渲染
            if (comm == null || string.IsNullOrEmpty(comm.RealMsg))
                return;
            Matrix trans;
            if (comm.RotateZ >= 0) {
                trans = Matrix.Multiply(Matrix.Multiply(
                    Matrix.Scaling(comm.ryValue, 1, 1)
                    , Matrix.RotationZ(comm.rzValue)
                    ), Matrix.Translation(comm.X, comm.Y, 0));
            } else {
                trans = Matrix.Translation(comm.X, comm.Y, 0);
            }
            // 载入Texture
            Texture texComment = GetCommentTexture(comm);
            int color = 0xffffff;
            double? alpha = (comm.A1 + (comm.A2 - comm.A1)
                * (current - comm.PlayTime) / comm.Life);
            color |= ((int)(alpha * 255)) << 24;
            string msg = comm.RealMsg;
            sprite.Transform = trans;
            sprite.Draw(texComment, Vector3.Empty, Vector3.Empty, color);
            if (selected)
                DrawRect(comm.X, comm.Y, comm.Width, comm.Height);
        }
        private void RenderFixedComment(BiliComment comm, double current,
            Device device, bool selected) {
            // 载入Texture
            Texture texComment = GetCommentTexture(comm);
            if (!comm.X.HasValue) {
                comm.X = GetX(comm.Width);
                if (comm.Mode == 5 && comm.X >= 5)
                    comm.X += 5;
                if (comm.Mode == 5) {
                    // 置顶
                    _listT.Removes(current);
                    comm.Y = GetY(_listT, comm.Height);
                    _listT.Add(new UsedPos(comm.Width, comm.Y.Value,
                        comm.Y.Value + comm.Height, comm.Life, current, 5, comm));
                } else if (comm.Mode == 4) {
                    // 底端
                    _listB.Removes(current);
                    comm.Y = GetRY(_listB, comm.Height);
                    _listB.Add(new UsedPos(comm.Width, comm.Y.Value,
                        comm.Y.Value + comm.Height, comm.Life, current, 4, comm));
                }
            }
            sprite.Transform = Matrix.Translation(comm.X.Value + 3, comm.Y.Value + 3, 0);
            sprite.Draw(texComment, Vector3.Empty, Vector3.Empty, -1);
            if (selected)
                DrawRect((int)comm.X, (int)comm.Y, comm.Width, comm.Height);
        }
        private void RenderScrollComment(BiliComment comm, double current,
            Device device, bool selected) {
            // 载入Texture
            Texture texComment = GetCommentTexture(comm);
            if (!comm.Y.HasValue) {
                if (comm.Mode == 1) {
                    // 滚动
                    _listS.RemoveScrolls(current);
                    comm.Y = GetY(_listS, comm.Height);
                    _listS.Add(new UsedPos(comm.Width, comm.Y.Value,
                        comm.Y.Value + comm.Height, comm.Life, current, 1, comm));
                } else {
                    // 逆向
                    _listNS.RemoveNScrolls(current);
                    comm.Y = GetY(_listNS, comm.Height);
                    _listNS.Add(new UsedPos(comm.Width, comm.Y.Value,
                        comm.Y.Value + comm.Height, comm.Life, current, 6, comm));
                }
            }
            if (comm.Mode == 1) {
                comm.X = (int)((1 - (current - comm.PlayTime) / comm.Life)
                    * (displaySize.Width + comm.Width - 76)) - comm.Width;
            } else {
                comm.X = (int)(((current - comm.PlayTime) / comm.Life)
                    * (displaySize.Width + comm.Width - 86)) - comm.Width + 86;
            }
            sprite.Transform = Matrix.Translation(comm.X.Value, comm.Y.Value, 0);
            sprite.Draw(texComment, Vector3.Empty, Vector3.Empty, -1);
            if (selected)
                DrawRect((int)comm.X, (int)comm.Y, comm.Width, comm.Height);
        }
        private int GetY(SortedLinkedList used, int height) {
            if (height > displaySize.Height - 9)
                return 0;
            int y = 1;
            if (used.Count == 0)
                return y;
            if (used.First.Top - 5 >= height)
                return y;
            if (used.Count == 1) {
                UsedPos pos = used.First;
                if (pos.Top < displaySize.Height / 2) {
                    if (pos.Bottom + height > displaySize.Height) {
                        return displaySize.Height - height;
                    }
                    return pos.Bottom;
                } else {
                    if (pos.Top - height < 0) {
                        return 0;
                    }
                    return pos.Top - height;
                }
            }
            used.Foreach((pos, next) => {
                if (next.Top - pos.Bottom >= height) {
                    y = pos.Bottom;
                    return true;
                }
                if (next == used.Last) {
                    if (displaySize.Height - next.Bottom >= height) {
                        y = next.Bottom;
                    } else {
                        y = (int)(random.NextDouble() * (displaySize.Height - height - 1)) + 1;
                    }
                }
                return false;
            });
            return y;
        }
        private int GetRY(SortedLinkedList used, int height) {
            if (height > displaySize.Height - 9)
                return 0;
            int y = displaySize.Height - 4;
            if (used.Count == 0)
                return y - height;
            if (y - used.Last.Bottom >= height)
                return y - height;

            if (used.Count == 1) {
                UsedPos pos = used.Last;
                if (pos.Bottom > displaySize.Height / 2) {
                    if (pos.Top - height < 0) {
                        return 0;
                    }
                    return pos.Top - height;
                } else {
                    if (pos.Bottom + height > displaySize.Height) {
                        return displaySize.Height - height;
                    }
                    return pos.Bottom;
                }
            }
            used.ForeachBack((pos, prev) => {
                if (pos.Top - prev.Bottom >= height) {
                    y = pos.Top - height;
                    return true;
                }
                if (prev == used.First) {
                    if (prev.Top >= height) {
                        y = prev.Top - height;
                    } else {
                        y = (int)(random.NextDouble() * (displaySize.Height - height - 4));
                    }
                }
                return false;
            });
            return y;
        }
        private int GetX(int width) {
            return (538 >= width) ? (displaySize.Width - width - 4) / 2 + 4 : 0;
        }
        private void DrawRect(int x, int y, int width, int height) {
            Vector2[] vecs = new Vector2[5];
            vecs[0] = new Vector2(x, y);
            vecs[1] = new Vector2(x + width, y);
            vecs[2] = new Vector2(x + width, y + height);
            vecs[3] = new Vector2(x, y + height);
            vecs[4] = vecs[0];
            ln.Begin();
            ln.Draw(vecs, -12336441);
            ln.End();
        }
        #endregion

        #region - DirectShow 控制 -
        protected override bool ProcessDialogKey(Keys keyData) {
            if (mediaControl != null) {
                if (keyData == Keys.F) {
                    if (playState == PlayState.Running)
                        PauseGraph();
                    videoStep.Step(1, null);
                    return true;
                }
            }
            return base.ProcessDialogKey(keyData);
        }
        protected override void WndProc(ref Message m) {
            switch (m.Msg) {
                case WM_PAINT: {
                        if (graphBuilder == null)
                            break;
                        try {
                            Render();
                        } catch { }
                        NativeMethods.InvalidateRect(this.Handle, IntPtr.Zero, 0);
                        return;
                    }
                case WM_GRAPHNOTIFY: {
                        EventCode evCode;
                        IntPtr p1, p2;
                        while (mediaEvent.GetEvent(out evCode, out p1, out p2, 0) == 0) {
                            mediaEvent.FreeEventParams(evCode, p1, p2);
                            if (evCode == EventCode.Complete)
                                OnClipCompleted();
                            else if (evCode == EventCode.Paused)
                                OnPaused();
                        }
                        break;
                    }
            }
            base.WndProc(ref m);
        }
        private void OnClipCompleted() {
            if ((mediaControl == null) || (mediaPosition == null))
                return;
            mediaPosition.put_CurrentPosition(0);
            PauseGraph();
        }
        private void OnPaused() {
            //RefreshPos();
        }
        private void OpenFile(string file) {
            try {
                InitializeGraphics();
                InitVMR9(file);
                mediaControl.Run();
                mediaControl.Pause();
                playState = PlayState.Paused;
                mediaPosition.get_Duration(out duration);
                Text = file.Substring(file.LastIndexOf('\\') + 1) + c_PlayerText;
            } catch (Exception e) {
                MessageBox.Show(this, "加载文件时出现异常！" + e.Message, "异常",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                this.Close();
            }
        }
        private string GetDurationStr(double duration) {
            int hours = (int)(Math.Floor(duration / 360));
            duration -= 60 * hours;
            int mins = (int)(Math.Floor(duration / 60));
            duration -= 60 * mins;
            int secs = (int)(Math.Floor(duration));
            return hours + ":"
                + mins.ToString("d2") + ":" + secs.ToString("d2");
        }
        private void RunGraph() {
            if (mediaControl != null) {
                int hr = mediaControl.Run();
                DsError.ThrowExceptionForHR(hr);
                playState = PlayState.Running;
            }
        }
        private void StopGraph() {
            if (mediaControl != null) {
                int hr = mediaControl.Stop();
                DsError.ThrowExceptionForHR(hr);
                playState = PlayState.Stopped;
            }
        }
        private void PauseGraph() {
            if (mediaControl != null) {
                int hr = mediaControl.Pause();
                DsError.ThrowExceptionForHR(hr);
                playState = PlayState.Paused;
            }
        }
        private void SetPosition(double current) {
            if (current < 0)
                current = 0;
            if (current >= duration) {
                OnClipCompleted();
            } else {
                nowPosition = current;
                frames = 0;
                mediaPosition.put_CurrentPosition(current);
            }
        }
        private bool CanDrag(Point pt) {
            if (pt.X >= 10 && pt.Y > 386 && pt.X <= 532 && pt.Y < 400)
                return true;
            return false;
        }
        private bool CanAdd(Point pt) {
            if (pt.X > 0 && pt.X < displaySize.Width && pt.Y > 0 && pt.Y < displaySize.Height)
                return true;
            return false;
        }
        private bool OutForm(Point pt) {
            if (pt.X < 0 || pt.Y < 0 || pt.X > ClientSize.Width || pt.Y > ClientSize.Height)
                return true;
            return false;
        }
        #endregion
    }
    internal enum PlayState {
        Init,
        Stopped,
        Paused,
        Running
    }
}
